package graphics;

import java.util.Observable;

import javax.swing.Timer;

public class AnimationClock extends Observable implements
		java.awt.event.ActionListener {
	// make it easy to refer to one second in milliseconds
	private static final int ONE_SECOND = 1000;

	// make it easy to change animation rate
	private static final int NUM_FRAMES_PER_SECOND = 40;

	// connect the model and the view
	private Animatable myAnimatable;

	private Timer myClock;

	private int myIdealFrameRate;

	private int myActualFrameRate;

	private int myNumSteps;

	private double myElapsedTime;

	private long myStartTime;

	private long myCurrentTime;

	public AnimationClock(Animatable a) {
		myAnimatable = a;
		myIdealFrameRate = myActualFrameRate = NUM_FRAMES_PER_SECOND;
		reset();
	}

	/**
	 * Retrieve the ideal framerate
	 * 
	 * @return
	 */
	public int getFrameRate() {
		return myIdealFrameRate;
	}

	/**
	 * Retrieve the actual framerate
	 * 
	 * The actual framerate is equal to the number of steps that take place in a single second
	 * @return
	 */
	public int getActualFrameRate() {
		return myActualFrameRate;
	}

	/**
	 * Retrieve the current time
	 * 
	 * The current time is how long the clock has been running
	 * @return
	 */
	public double getCurrentTime() {
		return toSeconds(myCurrentTime - myStartTime);
	}

	/**
	 * Reset the clock to its start state
	 *
	 */
	public void reset() {
		myStartTime = 0;
		myCurrentTime = 0;
		myElapsedTime = 0;
		myNumSteps = 0;
		myClock = null;
	}

	public void start() {
		myClock = new javax.swing.Timer(ONE_SECOND / myIdealFrameRate, this);
		myClock.start();
		myCurrentTime = myStartTime = System.currentTimeMillis();
	}

	public void stop() {
		pause();
		reset();
	}

	public void pause() {
		if (myClock != null) {
			myClock.stop();
			myClock = null;
		}
	}

	/**
	 * Default step
	 * 
	 * A convenience method which will step through the animatable at a set pace
	 *
	 */
	public void step() {
		step(toSeconds(ONE_SECOND / myIdealFrameRate));
	}

	/**
	 * Trigger a step
	 * 
	 * This is a wrapper method which can be overridden to add conditions on stepping.
	 * 
	 * @param elapsedTime
	 */
	public void doStep(double elapsedTime) {
		step(elapsedTime);
	}

	/**
	 * Step through the anitable object once
	 * 
	 * Stepping through the animation will cause the animatable to animate itself
	 * once.  This should change state and result in an updated view.
	 * @param elapsedTime
	 */
	public void step(double elapsedTime) {
		myNumSteps++;
		myElapsedTime += elapsedTime;
		if (myElapsedTime >= 1.0) {
			myActualFrameRate = myNumSteps;
			myNumSteps = 0;
			myElapsedTime = 0;
		}
		myAnimatable.animate();
		setChanged();
		notifyObservers(myAnimatable);
	}

	//------------------------------------------------------------
	// conversion functions
	protected double toSeconds(long milliSeconds) {
		return ((double) milliSeconds) / ONE_SECOND;
	}

	protected int toMilliSeconds(double seconds) {
		return (int) (seconds * ONE_SECOND);
	}

	public void actionPerformed(java.awt.event.ActionEvent e) {
		long lastTime = myCurrentTime;
		myCurrentTime = System.currentTimeMillis();
		doStep(toSeconds(myCurrentTime - lastTime));
	}
}
